#ifndef _sdl_ptr_h_
#define _sdl_ptr_h_

#include "sdl_exceptions.h"
#include "sdl_prim.h"
#include <iostream>

template<class obj_t>

class sdl_ptr {
    obj_t * __data;
    bool __hold;

public:

    sdl_ptr() : __data(NULL), __hold(false) {
    }

    /**
     * \brief Konstruktor inicjujący, explicit
     * Odwołania TYLKO do obiektów stworzonych przez operator new! Prawidłowa składnia:
     * @code
     * 	sdl_ptr<moj_obiekt> a(new moj_obiekt);
     *	// lub
     *	sdl_ptr<moj_obiekt> b = new moj_obiekt;
     * @endcode
     * NIEPRAWIDŁOWA SKŁADNIA:
     * @code
     * 	// ten kod generuje błąd typu double free
     * 	moj_obiekt a;
     * 	sdl_ptr<moj_obiekt> a_ptr = &a
     * @endcode
     * Obecnie nawet nie da się wymusić takiej inicjacji poprzez odpowiednio zdefiniowany operator adresu w sdl_prim
     *
     */
    sdl_ptr(obj_t * new_ptr) : __data(new_ptr), __hold(new_ptr == NULL ? false : true) {
        if (__hold) {
            __data->__add_ref();
        }
    }

    sdl_ptr(const sdl_ptr & old_ptr) : __hold(old_ptr.__hold) {
        __data = old_ptr.__data;
        __data->__add_ref();
    }

    ~sdl_ptr() {
        release();
    }

    /*
     * OPERATORY
     */
    operator obj_t *() const {
        if (__hold)
            return __data;
        else
            throw sdlex::exception("* on a null smart pointer");
    }

    obj_t * operator->() const {
        if (__hold)
            return __data;
        else
            throw sdlex::exception("-> on a null smart pointer");
    }

    const sdl_ptr & operator=(const sdl_ptr & s) {
        release();
        __data = s.__data;
        __data->__add_ref();
        __hold = s.__hold;
        return *this;
    }

    const sdl_ptr & operator=(obj_t * s) {
        throw sdlex::exception("illegal use of operator=(obj_t *s)");
    }

    bool operator==(const sdl_ptr & s) const {
        return __data == s.__data;
    }

    bool operator!=(const sdl_ptr & s) const {
        return __data != s.__data;
    }

    bool null() const {
        return !__hold;
    }

    operator bool() const {
        return __hold;
    }

    bool operator!() const {
        return !__hold;
    }

    /*
    ///musimy być absolutnie pewni, że to było przypisanie z operatora new! (bezpośrednio lub pośrednio)
    ///dlatego nie definiujemy działającego operatora tej postaci
    const sdl_ptr & operator=(obj_t * s){
            release();
            __data=s;
            __hold=true;
            // tu nie
            __data->__add_ref();
            return *this;
    }
     */

private:

    void release() {
        if (__hold && __data != NULL) {
            if (__data->__ref < 0)
                throw sdlex::exception(std::string("release(): smart pointer error on ") + __data->name());

            if (__data->__dec_ref() == 0) {
                delete __data;
            }
        }
        __hold = false;
    }
};

#endif //_sdl_ptr_h_

